package com.liaoyifan.core.filter;

import com.liaoyifan.core.annotation.NoAuth;
import com.liaoyifan.core.constant.Constants;
import com.liaoyifan.core.enums.AuthCodeEnum;
import com.liaoyifan.core.exception.AuthException;
import com.liaoyifan.core.properties.AuthorizationProperties;
import com.liaoyifan.core.util.Token;
import com.liaoyifan.core.valid.Lang;
import io.jsonwebtoken.Claims;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

@SuppressWarnings("unused")
public class AuthorizationFilter implements Filter {

    private static final Logger log =
            LogManager.getLogger(AuthorizationFilter.class.getSimpleName());

    private final HandlerExceptionResolver handlerExceptionResolver;
    private final RequestMappingHandlerMapping requestMappingHandlerMapping;
    private final AuthorizationProperties properties;

    public AuthorizationFilter(
            HandlerExceptionResolver handlerExceptionResolver,
            RequestMappingHandlerMapping requestMappingHandlerMapping,
            AuthorizationProperties properties) {
        this.handlerExceptionResolver = handlerExceptionResolver;
        this.requestMappingHandlerMapping = requestMappingHandlerMapping;
        this.properties = properties;
    }

    @Override
    public void doFilter(
            ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
            throws ServletException, IOException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        try {
            String servletPath = request.getServletPath();
            if (isNoAuthPath(servletPath) || isNoAuthMethod(request)) {
                chain.doFilter(servletRequest, servletResponse);
                return;
            }
            log.info("Authorization servletPath: {}", servletPath);
            Claims claims = Token.currentAccess(request);
            if (!Token.validAccess(claims.getId())) {
                throw new AuthException(AuthCodeEnum.ACCESS_TOKEN_INVALID);
            }
            if (!Token.hasPermission(claims.getSubject(), servletPath)) {
                throw new AuthException(AuthCodeEnum.INSUFFICIENT_PERMISSIONS);
            }
        } catch (Exception e) {
            this.handlerExceptionResolver.resolveException(
                    request, (HttpServletResponse) servletResponse, null, e);
            return;
        }
        chain.doFilter(servletRequest, servletResponse);
    }

    private boolean isNoAuthPath(String servletPath) {
        if (Lang.isEmpty(this.properties.getNoAuthPaths())) {
            return false;
        }
        return this.properties.getNoAuthPaths().stream()
                .anyMatch(path -> Constants.PATH_MATCHER.match(path, servletPath));
    }

    private boolean isNoAuthMethod(HttpServletRequest request) throws Exception {
        HandlerExecutionChain handlerChain = this.requestMappingHandlerMapping.getHandler(request);
        if (handlerChain != null
                && handlerChain.getHandler() instanceof HandlerMethod handlerMethod) {
            return handlerMethod.hasMethodAnnotation(NoAuth.class)
                    || handlerMethod.getBeanType().isAnnotationPresent(NoAuth.class);
        }
        return false;
    }
}
